from datetime import datetime
from typing import Dict

import numpy as np
import pandas as pd


class Analyzer:
    def __init__(self) -> None:
        self.record_list:list[Dict] = []
        self.start_date = None
        self.end_date = None
        self.item_list = []
        self.indicator_type = ''
        self.attribute_list = []
        self.compare_stock = {
            'name':None,
            'stock_code':None,
            'data':[]
        }
        self.indicator_list = []
        self.calculate_dict = {
            'daily_return_rate': self.getDailyReturnRate,
            'returns_of_annual': self.getReturnsOfAnnual,
            'variance_of_assets': self.getVarianceOfTotalAssets,
            'variance_of_returns': self.getVarianceOfReturns,
            'complexity_of_strategy': self.getComplexityofStrategy,
            'max_drawdown_duration': self.getMaxDrawdownDuration,
            'max_drawdown': self.getMaxDrawdown,
            'sharpe_ratio': self.getSharpeRatio,
            'sortino_ratio': self.geteSortinoRatio,
            'omega_ratio': self.getOmegaRatio,
        }
        self.name_dict = {
            'daily_return_rate': "日均回报率",
            'returns_of_annual': "年化回报率",
            'variance_of_assets': "资产变化标准差",
            'variance_of_returns': "回报率变化的标准差",
            'complexity_of_strategy': "策略复杂度",
            'max_drawdown_duration': "最大回撤持续时间",
            'max_drawdown': "最大回撤",
            'sharpe_ratio': "夏普比率",
            'sortino_ratio': "所提诺比率",
            'omega_ratio': "欧米茄比率",
        }
        

    def addRecord(self,temp_record:dict)->None:
        self.record_list.append(temp_record)
    
    def getDailyReturnRate(self):
    # 计算总共有多少天的数据
        total_days = len(self.record_list)

        # 初始资产和最终资产
        initial_asset = self.record_list[0]['data']['total_assets']
        final_asset = self.record_list[-1]['data']['total_assets']

        # 计算日平均收益率
        daily_return_rate = ((final_asset/initial_asset) ** (1/total_days)) - 1

        return daily_return_rate
    
    def getReturnsOfAnnual(self)->None:
        day_count = len(self.record_list)
        if day_count == 0:
            return 0
        year_count = day_count / 252
        # Get the 'total_assets' of the first and last item (year)
        initial_assets = self.record_list[0]['data']['total_assets']
        final_assets = self.record_list[-1]['data']['total_assets']

        # Use the mentioned formula to calculate the annualized return
        annual_return = (final_assets / initial_assets) ** (1 / year_count) - 1
        return annual_return     

    def getVarianceOfTotalAssets(self)->float:
        df = pd.DataFrame([item['data'] for item in self.record_list])
    # Calculate and return the standard deviation
        return df['total_assets'].std(ddof=0) 
    
    def getVarianceOfReturns(self)->float:
        total_assets_array = np.array([item['data']['total_assets'] for item in self.record_list])
        # Calculate daily returns
        daily_returns = (total_assets_array[1:] / total_assets_array[:-1]) - 1
        return np.var(daily_returns, ddof=0)
    
    def getComplexityofStrategy(self)->float:
        operations_count = 0
        for i in self.record_list:
            operations_count += len(i["operation_list"])
        complex_index = operations_count/len(self.record_list)
        return complex_index
    def getMaxDrawdownDuration(self):
        total_assets = [item['data']['total_assets'] for item in self.record_list]

        max_value = total_assets[0]
        max_drawdown_duration = 0
        current_max_drawdown_duration = 0

        for value in total_assets[1:]:
            if value >= max_value:
                max_value = value
                current_max_drawdown_duration = 0
            else:
                current_max_drawdown_duration += 1
                max_drawdown_duration = max(max_drawdown_duration, current_max_drawdown_duration)

        return max_drawdown_duration
    def getMaxDrawdown(self):
        # Create a list of total_assets values
        total_assets = [item['data']['total_assets'] for item in self.record_list]

        # Initialize the maximum value and maximum drawdown
        max_value = total_assets[0]
        max_drawdown = 0

        # Loop over all values
        for value in total_assets:
            # If a new maximum value is found, update max_value
            if value > max_value:
                max_value = value
            else:
                # Calculate the drawdown
                drawdown = (max_value - value) / max_value
                # If the drawdown is higher than the previous max drawdown, update max_drawdown
                if drawdown > max_drawdown:
                    max_drawdown = drawdown

        return max_drawdown
      
    def getSharpeRatio(self,risk_free_rate=0):
        # Extract 'total_assets' values into a NumPy array
        total_assets = np.array([item['data']['total_assets'] for item in self.record_list])

        # Calculate daily returns
        daily_returns = (total_assets[1:] / total_assets[:-1]) - 1

        # Calculate the excess returns by subtracting the risk-free rate from daily returns
        excess_returns = daily_returns - risk_free_rate

        # Calculate Sharpe ratio as the average of excess_returns divided by the standard deviation of excess_returns
        sharpe_ratio = np.mean(excess_returns) / np.std(excess_returns)

        return sharpe_ratio
    
    def geteSortinoRatio(self,risk_free_rate=0):
        # Extract 'total_assets' values into a NumPy array
        total_assets = np.array([item['data']['total_assets'] for item in self.record_list])

        # Calculate daily returns
        daily_returns = (total_assets[1:] / total_assets[:-1]) - 1


        # Calculate the excess returns by subtracting the risk-free rate from daily returns
        excess_returns = daily_returns - risk_free_rate

        # Calculate the downside deviation: standard deviation of negative returns only
        negative_returns = excess_returns[excess_returns < 0]
        downside_deviation = np.std(negative_returns, ddof=0)

        # Calculate the Sortino ratio as the mean of excess_returns divided by the downside deviation
        sortino_ratio = np.mean(excess_returns) / downside_deviation

        return sortino_ratio
    def getOmegaRatio(self, threshold_return=0.0):
        total_assets = [item['data']['total_assets'] for item in self.record_list]

        # Calculate returns
        returns = [total_assets[i+1]/total_assets[i] - 1 for i in range(len(total_assets)-1)]

        # Separate gains and losses
        gains = [ret for ret in returns if ret > threshold_return]
        losses = [abs(ret) for ret in returns if ret <= threshold_return]

        # Calculate the sum of gains and losses
        sum_gains = sum(gains) if gains else 0.0
        sum_losses = sum(losses) if losses else 0.0

        # Calculate the Omega ratio
        omega_ratio = sum_gains / sum_losses if sum_losses != 0 else float('inf')

        return omega_ratio    
    def setIndicator(self)->None:
        if self.indicator_type == 'type1':
            self.indicator_list.append({
                'name':'日均回报率',
                'value':self.getDailyReturnRate()
            })
            self.indicator_list.append({
                'name':'风险',
                'value':self.getVarianceOfReturns()
            })
            self.indicator_list.append({
                'name':'夏普比率',
                'value':self.getSharpeRatio()
            })
            self.indicator_list.append({
                'name':'日均回报率',
                'value':self.geteSortinoRatio()
            })
        elif self.indicator_type == 'type2':
            self.indicator_list.append({
                'name':'最大回撤',
                'value':self.getMaxDrawdown()
            })
            self.indicator_list.append({
                'name':'所提诺比率',
                'value':self.geteSortinoRatio()
            })
            self.indicator_list.append({
                'name':'回撤持续时间',
                'value':self.getMaxDrawdownDuration()
            })
            self.indicator_list.append({
                'name':'欧米茄比率',
                'value':self.getOmegaRatio()
            })
        else:
            for i in self.indicator_type:
                self.indicator_list.append({
                    'name':self.name_dict[i],
                    'value':self.calculate_dict[i]()
                })
    def getReturnList(self)->list:
        return_list = [0]
        for i in range(1, len(self.record_list)):
            previous_total_assets = self.record_list[i - 1]['data']['total_assets']
            current_total_assets = self.record_list[i]['data']['total_assets']
            daily_return = (current_total_assets - previous_total_assets) / previous_total_assets
            return_list.append(daily_return)
        return return_list
    
    def getCompareStockReturnList(self)->list:
        return_list = [0]
        for i in range(1, len(self.compare_stock['data'])):
            previous_total_assets = self.compare_stock['data'][i - 1]
            current_total_assets = self.compare_stock['data'][i]
            daily_return = (current_total_assets - previous_total_assets) / previous_total_assets
            return_list.append(daily_return)
        return return_list
    
    def getOperationRatio(self):
        
        empty_operation_list_count = 0
        total_assets_increased_count = 0
        total_assets_decreased_count = 0

        next_total_assets = self.record_list[1]['data']['total_assets']

        for i in range(len(self.record_list) - 1):
            record = self.record_list[i]
            next_record = self.record_list[i + 1]

            if not record['operation_list']:  # If operation list is empty
                empty_operation_list_count += 1
            else:  # If operation list is not empty, compare with the next day
                if next_record['data']['total_assets'] > record['data']['total_assets']:
                    total_assets_increased_count += 1
                elif next_record['data']['total_assets'] < record['data']['total_assets']:
                    total_assets_decreased_count += 1

        return {
            'empty':empty_operation_list_count,
            'increase':total_assets_increased_count,
            'decrease':total_assets_decreased_count
        }
    
        
    def normalizeArray(self,arr):
        min_val = min(arr)
        max_val = max(arr)

        # 计算范围防止除零错误
        range_val = max_val - min_val
        if range_val == 0:
            raise ValueError("最大值和最小值相同，无法归一化。")

        # 归一化列表
        normalized = [(x - min_val) / range_val for x in arr]

        return normalized
    
    def getBacktestReport(self)->dict:
        self.attribute_list.append({
            'label':'收益率',
            'data_list':self.getReturnList()
        })
        for i in self.attribute_list:
            i['data_list'] = self.normalizeArray(i['data_list'])
        
        self.setIndicator()
        report = {
            'attribute_list':self.attribute_list,
            'annual_returns':self.getReturnsOfAnnual(),
            'daily_returns':self.getDailyReturnRate(),
            'variance':self.getVarianceOfReturns(),
            'complex':self.getComplexityofStrategy(),
            'date_list':[],
            'operation_list':[],
            'total_assets_list':[],
            'return_list':self.getReturnList(),
            'compare_stock':{
                'name':self.compare_stock['name'],
                'return_list':self.getCompareStockReturnList()
            },
            'item_list':self.item_list,
            'indicator_list':self.indicator_list,
            'operation_return':self.getOperationRatio()
        }
        for i in self.record_list:
            report['date_list'].append(i['date'])
            report['operation_list'].append(i['operation_list'])
            report['total_assets_list'].append(i['data']['total_assets'])
        
        return report